# coding: utf-8
import base64
from pathlib import Path
from typing import Union

from PySide6.QtCore import Signal, QEvent, QSize, Qt, Property, QByteArray, QBuffer, QIODevice, QPropertyAnimation, \
    QRectF, QSizeF, QPointF, QUrl
from PySide6.QtGui import QImage, QPixmap, QImageReader, QMovie, QMouseEvent, QPaintEvent, QPainter, QPainterPath, \
    QColor
from PySide6.QtNetwork import QNetworkReply, QNetworkAccessManager, QNetworkRequest
from PySide6.QtWidgets import QLabel
from loguru import logger
from qfluentwidgets import isDarkTheme, FluentIconBase
from qfluentwidgets.common.overload import singledispatchmethod


class ImageLabel(QLabel):
    clicked = Signal(object)
    finished = Signal(object)

    @singledispatchmethod
    def __init__(self, parent=None):
        super().__init__(parent)
        self.image = QImage()
        self.__scaledContents = True
        self.__topLeftRadius = 0
        self.__topRightRadius = 0
        self.__bottomLeftRadius = 0
        self.__bottomRightRadius = 0
        self.__radius = 0
        self.__dataSrc = ""
        self.__light_color = QColor(255, 255, 255, 150)
        self.__dark_color = QColor(0, 0, 0, 150)
        self.__mask_opacity = 150
        self.__background_color = QColor(150, 150, 150, 50)
        self.__enable_mask = False
        self.__url = QUrl()
        self.manager = QNetworkAccessManager(self)

        self._postInit()

    @__init__.register
    def _(self, image: Union[str, QImage, QPixmap, Path], parent=None):
        self.__init__(parent)
        self.setImage(image)

    def _postInit(self):
        pass

    def _onFrameChanged(self, index: int):
        self.image = self.movie().currentImage()
        self.update()

    def setBorderRadius(self, topLeft: int, topRight: int, bottomLeft: int, bottomRight: int):
        """ 设置图像的边界半径 """
        self.__topLeftRadius = topLeft
        self.__topRightRadius = topRight
        self.__bottomLeftRadius = bottomLeft
        self.__bottomRightRadius = bottomRight
        self.update()

    def _onFinished(self, reply: QNetworkReply):
        if reply.error() == QNetworkReply.NetworkError.NoError:
            data = reply.readAll()
            image = QImage.fromData(data)
            self.setImage(image)
        else:
            logger.error(f"{reply.url().toString()}, Network error: {reply.errorString()}")
        reply.deleteLater()

    def setUrl(self, url: Union[str, QUrl], headers: dict = None):
        if not url:
            return
        self.__url = QUrl(url)
        request = QNetworkRequest(QUrl(url))
        if headers:
            for key, value in headers.items():
                request.setRawHeader(key.encode(), value.encode())
        reply = self.manager.get(request)
        reply.finished.connect(lambda: self._onFinished(reply))

    def getUrl(self) -> QUrl:
        return self.__url

    def setImage(self, image: Union[str, QImage, QPixmap, Path, FluentIconBase]):
        if isinstance(image, (str, Path)):
            reader = QImageReader(str(image))
            if reader.supportsAnimation():
                self.setMovie(QMovie(str(image)))
            else:
                image = reader.read()
        elif isinstance(image, QPixmap):
            image = image.toImage()
        elif isinstance(image, FluentIconBase):
            image = QImage(image.path())
        self.image = image
        self.update()
        self.finished.emit(self)

    def setPixmap(self, pixmap: Union[str, QImage, QPixmap, Path]):
        self.setImage(pixmap)

    def pixmap(self) -> QPixmap:
        return QPixmap.fromImage(self.image)

    def setMovie(self, movie: QMovie):
        super().setMovie(movie)
        self.movie().start()
        self.image = self.movie().currentImage()
        self.movie().frameChanged.connect(self._onFrameChanged)

    def scaledToWidth(self, width: int):
        if self.isNull():
            return

        h = int(width / self.image.width() * self.image.height())
        self.setFixedSize(width, h)
        self.setMinimumHeight(h)

        if self.movie():
            self.movie().setScaledSize(QSize(width, h))

    def scaledToHeight(self, height: int):
        if self.isNull():
            return

        w = int(height / self.image.height() * self.image.width())
        self.setFixedSize(w, height)

        if self.movie():
            self.movie().setScaledSize(QSize(w, height))

    def setScaledSize(self, size: QSize):
        if self.isNull():
            return

        self.setFixedSize(size)

        if self.movie():
            self.movie().setScaledSize(size)

    def sizeHint(self) -> QSize:
        if self.image.isNull():
            return super().sizeHint()
        else:
            return self.image.size()

    def isNull(self) -> bool:
        return self.image.isNull()

    def mouseReleaseEvent(self, event: QMouseEvent):
        pos = event.position().toPoint()
        if event.button() == Qt.MouseButton.LeftButton and self.rect().contains(pos):
            self.clicked.emit(self)
        super().mouseReleaseEvent(event)

    def setRadius(self, radius: int):
        self.__radius = radius
        self.__topLeftRadius = self.__topRightRadius = self.__bottomLeftRadius = self.__bottomRightRadius = radius
        self.update()

    def getRadius(self) -> int:
        return self.__radius

    def setBackgroundColor(self, color: QColor):
        self.__background_color = color
        self.update()

    def backgroundColor(self) -> QColor:
        return self.__background_color

    def setTopLeftRadius(self, radius: int):
        self.__topLeftRadius = radius
        self.update()

    def getTopLeftRadius(self) -> int:
        return self.__topLeftRadius

    def setTopRightRadius(self, radius: int):
        self.__topRightRadius = radius
        self.update()

    def getTopRightRadius(self) -> int:
        return self.__topRightRadius

    def setBottomLeftRadius(self, radius: int):
        self.__bottomLeftRadius = radius
        self.update()

    def getBottomLeftRadius(self) -> int:
        return self.__bottomLeftRadius

    def setBottomRightRadius(self, radius: int):
        self.__bottomRightRadius = radius
        self.update()

    def getBottomRightRadius(self) -> int:
        return self.__bottomRightRadius

    def setScaledContents(self, contents: bool):
        self.__scaledContents = contents
        self.update()

    def getScaledContents(self) -> bool:
        return self.__scaledContents

    def setDataSrc(self, src: str):
        self.__dataSrc = src

    def getDataSrc(self) -> str:
        return self.__dataSrc

    def _creatPainterPath(self):
        path = QPainterPath()
        w, h = self.width(), self.height()

        # top line
        path.moveTo(self.topLeftRadius, 0)
        path.lineTo(w - self.topRightRadius, 0)

        # top right arc
        d = self.topRightRadius * 2
        path.arcTo(w - d, 0, d, d, 90, -90)

        # right line
        path.lineTo(w, h - self.bottomRightRadius)

        # bottom right arc
        d = self.bottomRightRadius * 2
        path.arcTo(w - d, h - d, d, d, 0, -90)

        # bottom line
        path.lineTo(self.bottomLeftRadius, h)

        # bottom left arc
        d = self.bottomLeftRadius * 2
        path.arcTo(0, h - d, d, d, -90, -90)

        # left line
        path.lineTo(0, self.topLeftRadius)

        # top left arc
        d = self.topLeftRadius * 2
        path.arcTo(0, 0, d, d, -180, -90)
        return path

    def _creatPainterImage(self):
        if self.scaledContents:
            image = self.image.scaled(
                self.size() * self.devicePixelRatioF(),
                Qt.AspectRatioMode.KeepAspectRatioByExpanding,
                Qt.TransformationMode.SmoothTransformation
            )  # type: QImage

            iw, ih = image.width(), image.height()
            w = self.width() * self.devicePixelRatioF()
            h = self.height() * self.devicePixelRatioF()
            x, y = (iw - w) / 2, (ih - h) / 2
            image = image.copy(int(x), int(y), int(w), int(h))
            return image
        else:
            image = self.image.scaled(
                self.size() * self.devicePixelRatioF(),
                Qt.AspectRatioMode.IgnoreAspectRatio,
                Qt.TransformationMode.SmoothTransformation
            )
            return image

    def _creatPainterRect(self):
        return self.rect()

    def paintEvent(self, event: QPaintEvent):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.RenderHint.Antialiasing)
        painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform)
        painter.setRenderHint(QPainter.RenderHint.LosslessImageRendering)
        painter.setPen(Qt.PenStyle.NoPen)
        painter.setClipPath(self._creatPainterPath())
        rect = self._creatPainterRect()

        if self.isNull():
            if self.__background_color:
                painter.fillRect(rect, self.__background_color)
            painter.setFont(self.font())
            color = Qt.GlobalColor.white if isDarkTheme() else Qt.GlobalColor.black
            painter.setPen(color)
            painter.drawText(rect, Qt.AlignmentFlag.AlignCenter, self.text())
            painter.end()
            return

        painter.drawImage(rect, self._creatPainterImage())
        if self.__enable_mask:
            color: QColor = self.maskDarkColor if isDarkTheme() else self.maskLightColor
            color.setAlpha(self.maskOpacity)
            painter.fillRect(self.rect(), color)
        painter.end()

    def save(self, fileName: Union[str, QIODevice], *args, **kwargs):
        if self.isNull():
            return
        self.image.save(fileName, *args, **kwargs)

    def bytes(self, format: str = 'PNG') -> bytes:
        """
        将图片转换为字节数组
        :param format: 图片格式
        :return:
        """
        if self.isNull():
            return b''
        byte_array = QByteArray()
        buffer = QBuffer(byte_array)
        buffer.open(QIODevice.OpenModeFlag.WriteOnly)
        self.save(buffer, format.upper())
        return byte_array.data()

    def base64(self, format: str = 'PNG', prefix: str = 'data:image/{};base64,') -> str:
        """
        :param format: 图片格式
        :param prefix: 前缀
        :return: base64字符串
        """
        if self.image.isNull():
            return ''
        suffix = format.upper()
        if suffix == 'JPG':
            suffix = 'JPEG'
        prefix = prefix.format(suffix)
        base64_str = prefix + base64.b64encode(self.bytes(suffix)).decode()
        return base64_str

    def setLazyVisible(self, load: bool):
        src = self.getDataSrc()
        if not src:
            return
        if load:
            if self.isNull():
                if src.startswith('http'):
                    self.setUrl(src)
                else:
                    self.setImage(src)
        else:
            if self.isNull():
                return
            self.setImage(QImage())

    def clear(self):
        self.setImage(QImage())
        super().clear()

    def setMaskLightColor(self, light_color: Union[str, QColor]):
        self.__light_color = light_color
        self.update()

    def setMaskDarkColor(self, dark_color: Union[str, QColor]):
        self.__dark_color = dark_color
        self.update()

    def getMaskLightColor(self) -> QColor:
        return self.__light_color

    def getMaskDarkColor(self) -> QColor:
        return self.__dark_color

    def setMaskOpacity(self, opacity: int):
        self.__mask_opacity = opacity
        self.update()

    def getMaskOpacity(self):
        return self.__mask_opacity

    def setEnableMask(self, enable: bool):
        self.__enable_mask = enable
        self.update()

    def enableMask(self) -> bool:
        return self.__enable_mask

    scaledContents = Property(bool, getScaledContents, setScaledContents)
    topLeftRadius = Property(int, getTopLeftRadius, setTopLeftRadius)
    topRightRadius = Property(int, getTopRightRadius, setTopRightRadius)
    bottomLeftRadius = Property(int, getBottomLeftRadius, setBottomLeftRadius)
    bottomRightRadius = Property(int, getBottomRightRadius, setBottomRightRadius)
    radius = Property(int, getRadius, setRadius)
    url = Property(QUrl, getUrl, setUrl)
    dataSrc = Property(str, getDataSrc, setDataSrc)
    maskLightColor = Property(QColor, getMaskLightColor, setMaskLightColor)
    maskDarkColor = Property(QColor, getMaskDarkColor, setMaskDarkColor)
    maskOpacity = Property(int, getMaskOpacity, setMaskOpacity)


class ZoomImageLabel(ImageLabel):
    """ 缩放图像标签 """

    def _postInit(self):
        self.__zoomRatio = 1.0  # 缩放比例
        self.animation = QPropertyAnimation(self, b"zoomRatio", self)
        self.animation.setDuration(300)
        self.animation.setStartValue(1.0)
        self.animation.setEndValue(1.1)

        self.setRadius(5)
        self.setScaledContents(True)

    def setMinZoomRatio(self, minZoomRatio: float):
        self.animation.setStartValue(minZoomRatio)

    def setMaxZoomRatio(self, maxZoomRatio: float):
        self.animation.setEndValue(maxZoomRatio)

    def setZoomRatio(self, zoomRatio: float):
        self.__zoomRatio = zoomRatio
        self.update()

    def getZoomRatio(self) -> float:
        return self.__zoomRatio

    def event(self, event: QEvent):
        if event.type() == QEvent.Type.Enter:
            if not self.isNull():
                self.animation.setDirection(QPropertyAnimation.Direction.Forward)
                self.animation.start()
        elif event.type() == QEvent.Type.Leave:
            if not self.isNull():
                self.animation.setDirection(QPropertyAnimation.Direction.Backward)
                self.animation.start()
        return super().event(event)

    def _creatPainterRect(self):
        if self.image.isNull():
            return super()._creatPainterRect()

        image = self._creatPainterImage()
        imageRatio = image.width() / image.height()
        rectF = QRectF()
        rectF.setSize(QSizeF(imageRatio * self.height() * self.zoomRatio, self.height() * self.zoomRatio))
        rectF.moveCenter(QPointF(self.rect().center().x(), self.rect().center().y()))
        return rectF

    zoomRatio = Property(float, getZoomRatio, setZoomRatio)
